﻿#region License
/**	
The MIT License (MIT)

Copyright (c) 2013 Philipp Schillinger, Michael Alekseew, Andreas Braun, Steeven Zeiß

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. 
 **/
#endregion

using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Runtime.InteropServices;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using CLEyeMulticam;
using IndoorLocalization.UserInterface;

namespace IndoorLocalization
{
	/// <summary>
	/// Handles all program-wide issues and provides a list of all cams and mergers.
	/// </summary>
    /*static*/ class Program
    {

		/// <summary>
		/// A reference to the main window of this application.
		/// </summary>
		private static MainWindow mainWindow;

		private static List<SensorCam> cameras = new List<SensorCam>();
		/// <summary>
		/// A list of all added cameras.
		/// </summary>
		public static List<SensorCam> Cameras { 
			get { return cameras; }
		}

		private static SensorMerger merger;
		/// <summary>
		/// The default merger.
		/// </summary>
		public static SensorMerger Merger {
			get { return merger; }
		}

		private static List<SensorMerger> mergers;
		/// <summary>
		/// A list of all mergers.
		/// </summary>
		public static List<SensorMerger> Mergers {
			get { return mergers; }
		}

		private static List<RemoteMerger> remoteMergers;
		/// <summary>
		/// A list of all remote mergers.
		/// </summary>
		public static List<RemoteMerger> RemoteMergers {
			get { return remoteMergers; }
		}

		private static List<CLEyeCameraDevice> unusedDevices;
		/// <summary>
		/// A list of all CL-Eye devices that are currently unused.
		/// </summary>
		public static List<CLEyeCameraDevice> Devices {
			get { return unusedDevices; }
		}
		// devices that are currently in use
		private static List<CLEyeCameraDevice> usedDevices;


		// Preferences

		/// <summary>
		/// Contains all setting data.
		/// </summary>
		private static DataSet data;

		/// <summary>
		/// The path to the profile which is loaded on startup.
		/// </summary>
		public static String ProfilePath {
			get { return (String)data.Tables["Preferences"].Rows[0]["profile"]; }
			set { data.Tables["Preferences"].Rows[0]["profile"] = value; }
		}
		/// <summary>
		/// A pixel that is darker than this value is threated as a wall.
		/// </summary>
		public static float WallThreshold {
			get { return float.Parse((String)data.Tables["Preferences"].Rows[0]["threshold"]); }
			set {
				data.Tables["Preferences"].Rows[0]["threshold"] = value;
				FloorPlan.InitializeMapCoverage();
				if (mainWindow != null && FloorPlan.Map != null) {
					mainWindow.CreateWallMap();
					mainWindow.UpdateCoverageMap(null, false);
				}
			}
		}

        /// <summary>
        /// The number of generations used when optimizing.
        /// </summary>
        public static int OptimizeGenerations {
			get { return Convert.ToInt32(data.Tables["Optimize"].Rows[0]["optimizeGenerations"]); }
			set { data.Tables["Optimize"].Rows[0]["optimizeGenerations"] = value; }
        }
        /// <summary>
        /// The number of childs per parent used when optimizing.
        /// </summary>
        public static int OptimizeChildFactor {
			get { return Convert.ToInt32(data.Tables["Optimize"].Rows[0]["optimizeChildFactor"]); }
			set { data.Tables["Optimize"].Rows[0]["optimizeChildFactor"] = value; }
        }
        /// <summary>
        /// The number of groups used when optimizing.
        /// </summary>
        public static int OptimizeGroups {
			get { return Convert.ToInt32(data.Tables["Optimize"].Rows[0]["optimizeGroups"]); }
			set { data.Tables["Optimize"].Rows[0]["optimizeGroups"] = value; }
        }

		/// <summary>
		/// The port used for network communication.
		/// </summary>
		public static int NetworkPort {
			get { return Convert.ToInt32(data.Tables["Network"].Rows[0]["port"]); }
		}

		/// <summary>
		/// The total aperture of a camera.
		/// This means a cam has a aperture of Aperture/2 in each direction.
		/// </summary>
		public static int CamAperture {
			get { return Convert.ToInt32(data.Tables["Camera"].Rows[0]["aperture"]); }
		}
		/// <summary>
		/// The width of a frame captured by the cameras.
		/// </summary>
		public static int FrameWidth {
			get { return Convert.ToInt32(data.Tables["Camera"].Rows[0]["frameWidth"]); }
		}
		/// <summary>
		/// The height of a frame captured by the cameras.
		/// </summary>
		public static int FrameHeight {
			get { return Convert.ToInt32(data.Tables["Camera"].Rows[0]["frameHeight"]); }
		}
		/// <summary>
		/// The factor how much the pitch of a camera influences the perspective view.
		/// </summary>
		public static double PitchTransFactor {
			get { return Convert.ToDouble(data.Tables["Camera"].Rows[0]["pitchTransFactor"]); }
			set { data.Tables["Camera"].Rows[0]["pitchTransFactor"] = value; }
		}

		/// <summary>
		/// Minimum distance between two entities.
		/// Entities that are closer than this value are assumed to describe the same object.
		/// </summary>
		public static float EntityNearDistance {
			get { return (float)Convert.ToDouble(data.Tables["Algorithm"].Rows[0]["entityNearDistance"]); }
		}
		/// <summary>
		/// Maximum distance between the prediction of the next timestep and a real entity
		/// in which the real entitiy is considered to be the sequel.
		/// </summary>
		public static float EntitySequelTolerance {
			get { return (float)Convert.ToDouble(data.Tables["Algorithm"].Rows[0]["entitySequelTolerance"]); }
		}
		/// <summary>
		/// Minimum distance between two x-coordinates of centers of rectangles.
		/// </summary>
		public static int MinRectXDistance {
			get { return Convert.ToInt32(data.Tables["Algorithm"].Rows[0]["minRectXDistance"]); }
		}
		/// <summary>
		/// Minimum width of a rectangle relative to the frame width to be detected as motion.
		/// </summary>
		public static double MinRelRectWidth {
			get { return Convert.ToDouble(data.Tables["Algorithm"].Rows[0]["minRelRectWidth"]); }
		}
		/// <summary>
		/// Minimum height of a rectangle relative to the frame width to be detected as motion.
		/// </summary>
		public static double MinRelRectHeight {
			get { return Convert.ToDouble(data.Tables["Algorithm"].Rows[0]["minRelRectHeight"]); }
		}

		/// <summary>
		/// All color values below this threshold will be ignored for motion detection.
		/// </summary>
		public static double PrepThreshold {
			get { return Convert.ToDouble(data.Tables["Preprocessing"].Rows[0]["prepThreshold"]); }
			set { data.Tables["Preprocessing"].Rows[0]["prepThreshold"] = value; }
		}
		/// <summary>
		/// How often the erode should be done (reduce noise).
		/// </summary>
		public static int PrepErode {
			get { return Convert.ToInt32(data.Tables["Preprocessing"].Rows[0]["prepErode"]); }
			set { data.Tables["Preprocessing"].Rows[0]["prepErode"] = value; }
		}
		/// <summary>
		/// How often the dilate should be done (results in larger white areas).
		/// </summary>
		public static int PrepDilate {
			get { return Convert.ToInt32(data.Tables["Preprocessing"].Rows[0]["prepDilate"]); }
			set { data.Tables["Preprocessing"].Rows[0]["prepDilate"] = value; }
		}



        /// <summary>
        /// Starting point of the application
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

			// set up preferences data
			data = new DataSet("IndoorLocalizationData");
			// preferences: can be set during runtime via the preferences form
			data.Tables.Add("Preferences");
			data.Tables["Preferences"].Columns.Add("profile");
			data.Tables["Preferences"].Columns.Add("threshold");
			data.Tables["Preferences"].Rows.Add(new object[]{
				"",			// default profile
				0.5f,		// default wall threshold
			});
			// optimize: used to store last parameters of optimization
			data.Tables.Add("Optimize");
			data.Tables["Optimize"].Columns.Add("optimizeGenerations");
			data.Tables["Optimize"].Columns.Add("optimizeChildFactor");
			data.Tables["Optimize"].Columns.Add("optimizeGroups");
			data.Tables["Optimize"].Rows.Add(new object[]{
                10,         // default generations
                8,          // default child factor
                3           // default groups
			});
			// network: store network parameters, read-only
			data.Tables.Add("Network");
			data.Tables["Network"].Columns.Add("port");
			data.Tables["Network"].Rows.Add(new object[]{
				48000		// default port
			});
			// camera: store camera parameters, read-only
			data.Tables.Add("Camera");
			data.Tables["Camera"].Columns.Add("aperture");
			data.Tables["Camera"].Columns.Add("frameWidth");
			data.Tables["Camera"].Columns.Add("frameHeight");
			data.Tables["Camera"].Columns.Add("pitchTransFactor");
			data.Tables["Camera"].Rows.Add(new object[]{
				75,			// default aprture
				640,		// default frameWidth
				480,		// default frameHeight
				5			// default pitchTransFactor
			});
			// algorithm: store algorithm parameters, read-only
			data.Tables.Add("Algorithm");
			data.Tables["Algorithm"].Columns.Add("entityNearDistance");
			data.Tables["Algorithm"].Columns.Add("entitySequelTolerance");
			data.Tables["Algorithm"].Columns.Add("minRectXDistance");
			data.Tables["Algorithm"].Columns.Add("minRelRectWidth");
			data.Tables["Algorithm"].Columns.Add("minRelRectHeight");
			data.Tables["Algorithm"].Rows.Add(new object[]{
				25,			// default entityNearDistance
				25,			// default entitySequelTolerance
				40,			// default minRectXDistance
				0.05f,		// default minRelRectWidth
				0.1f		// default minRelRectHeight
			});
			// preprocessing: store preprocessing parameters
			data.Tables.Add("Preprocessing");
			data.Tables["Preprocessing"].Columns.Add("prepThreshold");
			data.Tables["Preprocessing"].Columns.Add("prepErode");
			data.Tables["Preprocessing"].Columns.Add("prepDilate");
			data.Tables["Preprocessing"].Rows.Add(new object[]{
				15,			// default prepThreshold
				3,			// default prepErode
				10			// default prepDilate
			});
			LoadSettings();

			// initialize mergers
			mergers = new List<SensorMerger>();
			merger = new LocalMerger("Default Merger");
			mergers.Add(merger);
			remoteMergers = new List<RemoteMerger>();

			// collect all connected cams and initialize them for further use
			// --------------------
			// The CL-Eye-Driver is not able to react if a cam is added or removed after the
			// application was started, so this work can be done when starting the applicartion.
			// Furthermore, devices that have been started and removed afterwards are not able
			// to start again. So they exist the whole time the application is running and are
			// stored in the list of unused devices if unused.
			// --------------------
			unusedDevices = new List<CLEyeCameraDevice>(CLEyeCameraDevice.CameraCount);
			usedDevices = new List<CLEyeCameraDevice>(CLEyeCameraDevice.CameraCount);
			for (int i = 0; i < CLEyeCameraDevice.CameraCount; i++) {
				CLEyeCameraDevice device = new CLEyeCameraDevice();
				device.Create(CLEyeCameraDevice.CameraUUID(i));
				device.AutoExposure = true;
				device.AutoGain = true;
				device.AutoWhiteBalance = true;
				unusedDevices.Add(device);
			}
			
			mainWindow = new MainWindow();
			Application.Run(mainWindow);
        }




		/// <summary>
		/// Sets the status of the application to the given text.
		/// </summary>
		/// <param name="text">New status of the application.</param>
		public static void SetStatus(String text) {
			mainWindow.Invoke(new MethodInvoker(() => {
				mainWindow.Status = text;
			}));
		}

		/// <summary>
		/// Sets the status of the application for a short period of time to the given text.
		/// Can be used to display short notifications.
		/// </summary>
		/// <param name="text">The status message to be shown.</param>
		public static void ShowStatusMessage(String text) {
			mainWindow.BeginInvoke(new MethodInvoker(() => {
				String oldStatus = mainWindow.Status;
				mainWindow.Status = text;
				Thread.Sleep(3000);
				mainWindow.Status = oldStatus;
			}));
		}

		/// <summary>
		/// Adds a new text entry to the log.
		/// This call is directed to the main window.
		/// </summary>
		/// <param name="text">Text that should be logged.</param>
		public static void Log(String text) {
			mainWindow.Log(text);
		}
		/// <summary>
		/// Adds a new warning message to the log.
		/// This call is directed to the main window.
		/// </summary>
		/// <param name="text">Text that should be logged.</param>
		public static void Warn(String text) {
			mainWindow.Warn(text);
		}
		/// <summary>
		/// Adds a new error message to the log.
		/// This call is directed to the main window.
		/// </summary>
		/// <param name="text">Text that should be logged.</param>
		public static void Error(String text) {
			mainWindow.Error(text);
		}

		/// <summary>
		/// Reloads the merger list in the GUI.
		/// Calling this method is neccessary whenever adding a new merger.
		/// </summary>
		public static void RefreshMergerList() {
			mainWindow.RefreshMergerList();
		}

		/// <summary>
		/// Call this method each time the position or rotation of a camera changes
		/// (BEFORE with false AND AFTERWARDS with true) to keep the coverage map up-to-date.
		/// </summary>
		/// <param name="cam">The cam that changed position. If null, a pure refresh will be done.</param>
		/// <param name="add"><c>false</c> before moving, <c>true</c> afterwards.</param>
		public static void UpdateCoverageMap(SensorCam cam, bool add) {
			mainWindow.UpdateCoverageMap(cam, add);
		}

		/// <summary>
		/// Initializes a list of cams that has been deserialized.
		/// </summary>
		/// <param name="cams">The cams to be loaded.</param>
		public static void LoadCameras(List<SensorCam> cams) {
			// update corresponding lists
			mergers.Clear();
			cameras = cams;
			bool needNetwork = false;

			foreach (SensorCam cam in cameras) {
				if (cam.Merger.IsLocal()) {

					SensorMerger equalMerger = mergers.Find(new Predicate<SensorMerger>(delegate(SensorMerger current) {
						return current.Name.Equals(cam.Merger.Name);
					}));

					// set the merger of each cam
					if (equalMerger == null) {
						if (cam.Merger.Name == "Default Merger") {
							merger = cam.Merger;
						} else {
							mergers.Add(cam.Merger);
						}
					} else {
						cam.Merger = equalMerger;
					}
				} else {
					needNetwork = true;
				}
			}

			// no cam had the default merger (perhaps because no cam was loaded)
			if (merger == null) {
				merger = new LocalMerger("Default Merger");
			}
			// add default merger to the list of mergers
			mergers.Add(merger);

			// enable network if remote mergers needed
			if (needNetwork) {
				mainWindow.StartNetwork();
				Log("Network has been started because it is needed");
			}

			// start the cams
			foreach (SensorCam cam in cameras) {
				cam.StartCam();
			}
		}

		/// <summary>
		/// Marks the device with the given Guid as in use.
		/// </summary>
		/// <param name="guid">The Guid of the device.</param>
		/// <returns>The device with the given Guid for further use.</returns>
		public static CLEyeCameraDevice UseDevice(Guid guid) {
			CLEyeCameraDevice device = unusedDevices.Find(new Predicate<CLEyeCameraDevice>(target => {
				return target.GUID.Equals(guid);
			}));
			if (device == null) return null;

			lock (unusedDevices) {
				//unusedDevices.Remove(device);
			}
			usedDevices.Add(device);
			return device;
		}
		/// <summary>
		/// Marks the device with the given Guid as not in use.
		/// </summary>
		/// <param name="guid">The Guid of the device.</param>
		public static void FreeDevice(Guid guid) {
			CLEyeCameraDevice device = usedDevices.Find(new Predicate<CLEyeCameraDevice>(target => {
				return target.GUID.Equals(guid);
			}));
			if (device == null) return;

			usedDevices.Remove(device);
			lock (unusedDevices) {
				unusedDevices.Add(device);
			}
		}

		/// <summary>
		/// Opens the preferences file and reads the last saved data.
		/// </summary>
		public static void LoadSettings() {
			if (File.Exists(".preferences")) {
				for (int i = 0; i < data.Tables.Count; i++) {
					data.Tables[i].Rows.RemoveAt(0); // remove standard values
				}
				data.ReadXml(".preferences");
			}
		}

		/// <summary>
		/// Opens the preferences file to save the current preferences.
		/// </summary>
		public static void SaveSettings() {
			data.AcceptChanges();
			data.WriteXml(".preferences");
		}

		/// <summary>
		/// Pause all cams of the program.
		/// </summary>
		public static void CamPause() {
			foreach (SensorCam cam in cameras)
				cam.Pause();
		}

		/// <summary>
		/// Resume all cams of the program.
		/// </summary>
		public static void CamResume() {
			foreach (SensorCam cam in cameras)
				cam.Resume();
		}

    }
}
